import path from 'node:path';
import { promises as fs } from 'node:fs';
import { randomUUID, createHash, randomBytes } from 'node:crypto';
import * as semver from 'semver';
import { exec as sudoExec } from 'sudo-prompt';
import Database from 'better-sqlite3';
import { CursorPaths } from '../../src/types';

// This interface should be in `vite-env.d.ts` but defined here for clarity
// until the project structure is finalized.
// interface CursorPaths {
//     storage_path: string;
//     sqlite_path: string;
//     machine_id_path: string;
//     cursor_path: string;
//     updater_path: string;
//     update_yml_path: string;
//     product_json_path: string;
// }

interface NewIds {
    'telemetry.devDeviceId': string;
    'telemetry.macMachineId': string;
    'telemetry.machineId': string;
    'telemetry.sqmId': string;
    'storage.serviceMachineId': string;
    machineId: string;
    machineIdV2: string;
}

/**
 * Generates a new set of machine IDs, strictly following the Python script's logic.
 */
function generateNewIds(): NewIds {
    const devDeviceId = randomUUID();
    const machineId = createHash('sha256').update(randomBytes(32)).digest('hex');
    const macMachineId = createHash('sha512').update(randomBytes(64)).digest('hex');
    const sqmId = `{${randomUUID().toUpperCase()}}`;

    return {
        'telemetry.devDeviceId': devDeviceId,
        'telemetry.macMachineId': macMachineId,
        'telemetry.machineId': machineId,
        'telemetry.sqmId': sqmId,
        'storage.serviceMachineId': devDeviceId,
        machineId,
        machineIdV2: createHash('sha256').update(machineId).digest('hex'),
    };
}

/**
 * Updates the storage.json file.
 */
async function updateStorageJson(storagePath: string, newIds: NewIds): Promise<void> {
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
    const backupPath = `${storagePath}.bak.${timestamp}`;
    await fs.copyFile(storagePath, backupPath);

    const content = await fs.readFile(storagePath, 'utf-8');
    const data = JSON.parse(content);

    Object.assign(data, newIds);

    await fs.writeFile(storagePath, JSON.stringify(data, null, 4));
}

/**
 * Updates the state.vscdb SQLite database using better-sqlite3.
 * This is the recommended and most stable approach for Electron.
 */
async function updateSqliteDb(dbPath: string, newIds: NewIds): Promise<void> {
    try {
        const db = new Database(dbPath);

        // Ensure the table exists, similar to the original Python script.
        db.exec(`
            CREATE TABLE IF NOT EXISTS "ItemTable" (
                "key" TEXT NOT NULL UNIQUE,
                "value" TEXT,
                PRIMARY KEY("key")
            )
        `);

        // Use a prepared statement for efficiency and security.
        const stmt = db.prepare('INSERT OR REPLACE INTO ItemTable (key, value) VALUES (?, ?)');

        // Run the update in a transaction for atomicity.
        const runTransaction = db.transaction((items) => {
            for (const item of items) {
                stmt.run(item.key, item.value);
            }
        });

        const itemsToUpdate = [
            { key: 'storage.machineId', value: newIds.machineId },
            { key: 'storage.machineIdV2', value: newIds.machineIdV2 },
        ];
        
        runTransaction(itemsToUpdate);

        db.close();
    } catch (error) {
        console.error(`Failed to update SQLite database at ${dbPath}:`, error);
        // We'll re-throw the error to make the main process aware of the failure.
        throw error;
    }
}

/**
 * Updates the machineId file.
 */
async function updateMachineIdFile(machineIdPath: string, devDeviceId: string): Promise<void> {
    await fs.mkdir(path.dirname(machineIdPath), { recursive: true });

    try {
        await fs.stat(machineIdPath);
        const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
        const backupPath = `${machineIdPath}.bak.${timestamp}`;
        await fs.copyFile(machineIdPath, backupPath);
    } catch (e) {
        // file doesn't exist, no need to back up.
    }
    
    await fs.writeFile(machineIdPath, devDeviceId);
}

/**
 * Main function to reset all machine IDs.
 * @param paths - An object containing all necessary paths to Cursor files.
 */
export async function resetMachineId(paths: CursorPaths): Promise<void> {
    const newIds = generateNewIds();
    
    await updateStorageJson(paths.storage_path, newIds);
    await updateSqliteDb(paths.sqlite_path, newIds); 
    await updateMachineIdFile(paths.machine_id_path, newIds['telemetry.devDeviceId']);

    const cursorAppPath = path.join(paths.cursor_path, "resources", "app");
    const packageJsonPath = path.join(cursorAppPath, "package.json");
    const mainJsPath = path.join(cursorAppPath, "out/main.js");
    const workbenchJsPath = path.join(cursorAppPath, "out/vs/workbench/workbench.desktop.main.js");

    await modifyWorkbenchJs(workbenchJsPath);
    
    const isVersionGte = await checkCursorVersion(packageJsonPath);
    if (isVersionGte) {
        await modifyMainJs(mainJsPath);
    }

    await updateWindowsRegistry();
}

/**
 * Modifies the main.js file to patch getMachineId functions.
 */
async function modifyMainJs(mainJsPath: string): Promise<void> {
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
    const backupPath = `${mainJsPath}.bak.${timestamp}`;
    await fs.copyFile(mainJsPath, backupPath);

    let content = await fs.readFile(mainJsPath, 'utf-8');

    const patterns = {
        'async getMachineId\\(\\)\\{return [^??]+\\?\\?([^}]+)\\}': 'async getMachineId(){return $1}',
        'async getMacMachineId\\(\\)\\{return [^??]+\\?\\?([^}]+)\\}': 'async getMacMachineId(){return $1}',
    };

    for (const [pattern, replacement] of Object.entries(patterns)) {
        content = content.replace(new RegExp(pattern, 'g'), replacement);
    }

    await fs.writeFile(mainJsPath, content, 'utf-8');
}

/**
 * Modifies the workbench.desktop.main.js file with various patches.
 */
async function modifyWorkbenchJs(workbenchJsPath: string): Promise<void> {
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
    const backupPath = `${workbenchJsPath}.bak.${timestamp}`;
    await fs.copyFile(workbenchJsPath, backupPath);

    let content = await fs.readFile(workbenchJsPath, 'utf-8');

    const replacements = {
        '<div>Pro Trial': '<div>Pro',
        'py-1">Auto-select': 'py-1">Bypass-Version-Pin',
        'async getEffectiveTokenLimit(e){const n=e.modelName;if(!n)return 2e5;': 'async getEffectiveTokenLimit(e){return 9000000;const n=e.modelName;if(!n)return 9e5;',
        'var DWr=ne("<div class=settings__item_description>You are currently signed in with <strong></strong>.");': 'var DWr=ne("<div class=settings__item_description>You are currently signed in with <strong></strong>. <h1>Pro</h1>");',
        'notifications-toasts': 'notifications-toasts hidden'
    };

    for (const [oldStr, newStr] of Object.entries(replacements)) {
        content = content.replace(oldStr, newStr);
    }

    await fs.writeFile(workbenchJsPath, content, 'utf-8');
}

/**
 * Checks if the Cursor version is greater than or equal to 0.45.0.
 */
async function checkCursorVersion(packageJsonPath: string): Promise<boolean> {
    try {
        const content = await fs.readFile(packageJsonPath, 'utf-8');
        const pkg = JSON.parse(content);
        const version = pkg.version;
        if (!version || typeof version !== 'string') {
            return false;
        }
        return semver.gte(version, '0.45.0');
    } catch (error) {
        console.error('Error checking cursor version:', error);
        return false;
    }
}

/**
 * Updates Windows registry keys with new GUIDs, requesting admin privileges.
 */
async function updateWindowsRegistry(): Promise<void> {
    if (process.platform !== 'win32') {
        return;
    }

    const newMachineGuid = randomUUID();
    const newSqmId = `{${randomUUID().toUpperCase()}}`;

    const commands = [
        `reg add "HKLM\\SOFTWARE\\Microsoft\\Cryptography" /v MachineGuid /t REG_SZ /d "${newMachineGuid}" /f`,
        `reg add "HKLM\\SOFTWARE\\Microsoft\\SQMClient" /v MachineId /t REG_SZ /d "${newSqmId}" /f`
    ];

    const command = commands.join(' && ');

    return new Promise((resolve) => {
        sudoExec(command, { name: 'Cursor Tool' }, (error, _stdout, stderr) => {
            if (error) {
                console.error('Failed to update registry with admin privileges:', error);
            }
            if (stderr) {
                console.error('Error during registry update:', stderr);
            }
            resolve();
        });
    });
} 